
#include <cstdio>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <string>
#include <vector>

#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
namespace po = boost::program_options;

#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;

#include "bocat/common/md5.hpp"

#include "binindex.hpp"

using namespace std;

#define	DEFAULT_TIMES	32

#define	N_ROUND	100000


float
getTimeDuration( ptime time_begin, ptime time_end )
{
	time_period	tp( time_begin, time_end );
	time_duration	td = tp.length();
	long	time_last_ms = td.total_milliseconds();
	float	time_last_s = time_last_ms / 1000.0f;

	return time_last_s;
}


const char*
getTLD( const char* url )
{
    static char s_tld[ 256 ];

    memset( s_tld, 0, sizeof(s_tld) );

    // url format :
    //      com.abc.www/index.php
    const char* p = strchr(url, '/');
    if ( NULL == p ) {
        snprintf(s_tld, sizeof(s_tld) - 1, "%s", url);
    }
    else {
        strncpy( s_tld, url, p - url );
        char * p = strchr( s_tld, ':' );
        if ( NULL != p ) {
            *p = '\0';
        }
    }
    
    char* start_p = s_tld;
    char* stop_p = strchr( start_p, '.' );
    if  ( NULL == stop_p ) {
        return  s_tld;
    }

    start_p = stop_p + 1;
    while ( NULL != stop_p ) {
        if ( (0 == strncmp(start_p, "com.", 4)) ||
             (0 == strncmp(start_p, "net.", 4)) ||
             (0 == strncmp(start_p, "edu.", 4)) ||
             (0 == strncmp(start_p, "gov.", 4)) ||
             (0 == strncmp(start_p, "org.", 4)) ||
             (0 == strncmp(start_p, "info.", 5)) 
           ) {
            stop_p = strchr( start_p, '.' );
            if ( NULL == stop_p ) {
                break;
            }
            start_p = stop_p + 1;
        }
        else {
            stop_p = strchr( start_p, '.' );
            if ( NULL == stop_p ) {
                break;
            }
            *stop_p = '\0';
            break;
        }
    }

    return  s_tld;
}


#define INI_FILENAME    "store.ini"
#define INI_SECTION     "fetcab::binindex"



void	test_001( const std::string& db_home, int key_size, int tables )
{

	fetcab::BinIndex	bi( INI_FILENAME, INI_SECTION );

	// test PUT
	{
		std::cerr << "testing PUT ...\n";
		ptime	time_start = microsec_clock::local_time();
		ptime	time_start_R = time_start;
		u_int32_t	count_down_R = N_ROUND;
		u_int32_t	count_round = 0;
        
        long    times = 0;

		while (true) {
            std::string line;
            std::getline( cin, line );
            if ( cin.eof() ) {
                break;
            }
            boost::trim( line );
            if ( line.size() < 1 ) {
                continue;
            }

            const char* tld = getTLD( line.c_str() );
            //std::cerr << line << " --> " << tld << std::endl;
            string  table( tld );

            string  data("ABCDEFGH");

			bocat::common::MD5	m;
			m.update( line.c_str(), line.size() );
			string	skey( m.digest(), key_size );

            ++times;

			int rc = bi.put( table, skey, data );
			if ( rc < 0 ) {
				std::cerr << "fail to PUT KEY: " << line << std::endl;
				return;
			}

			--count_down_R;
			if (0 == count_down_R) {
				ptime   time_stop_R = microsec_clock::local_time();
				float   time_last_s = getTimeDuration( time_start_R, time_stop_R );
				float   tps = N_ROUND / time_last_s;
				std::cerr << "  R-PUT " << ++count_round << "*" << N_ROUND << " records last " << std::setprecision(3) << std::fixed << time_last_s << " seconds, tps=" << tps << std::resetiosflags(std::ios::floatfield) << std::setprecision(6) << std::endl;

				time_start_R = time_stop_R;
				count_down_R = N_ROUND;
			}
		}

		ptime	time_stop = microsec_clock::local_time();
		float	time_last_s = getTimeDuration( time_start, time_stop );
		float	tps = times / time_last_s;
		std::cerr << "    PUT " << times << " records last " << std::setprecision(3) << std::fixed << time_last_s << " seconds, tps=" << tps << std::resetiosflags(std::ios::floatfield) << std::setprecision(6) << std::endl;
	}

}


int	main(int argc, char* argv[])
{
	std::string	db_file;
	bool	verbose = false;
	int		key_size = 0;
    int     tables = 0;

    try {

        po::options_description desc("Allowed options");
        desc.add_options()
            ("help,h", "produce help message")
            ("verbose,v", "output verbose message")
			("key-size", po::value<int>(&key_size)->default_value(16), "size of key in bytes (128-bit md5)")
			("tables", po::value<int>(&tables)->default_value(4), "number of tables to hash keys")
            ("db-file", po::value<std::string>(&db_file)->default_value("/tmp/fetcab/binindex/test.tch"), "db filename")
        ;   

        po::variables_map vm;    
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);    

        if (vm.count("help")) {
            std::cout << desc << "\n";
            return 1;
        }   

		if (vm.count("verbose")) {
			verbose = true;
		}
    }   
    catch(std::exception& e) {
        std::cerr << "error: " << e.what() << "\n";
        return 1;
    }   
    catch(...) {
        std::cerr << "Exception of unknown type!\n";
		return 2;
    }   

	// check if data-root exists
	boost::filesystem::path	db_file_path( db_file );
	boost::filesystem::path	db_home_path = db_file_path.branch_path();
	if ( !boost::filesystem::exists( db_home_path ) ) {
		try {
			boost::filesystem::create_directories( db_home_path );
		}
		catch( std::exception& e ) {
			std::cerr << "fail to create data-root : " << db_home_path.string() << " -- " << e.what() << std::endl;
			return	1;
		}
	}
	
	if ( key_size > 16 ) {
		key_size = 16;
	}

	test_001( db_file, key_size, tables );


	return	0;
}

